import {
  Directive,
  ElementRef,
  OnDestroy,
  OnInit,
  ViewContainerRef,
  ComponentFactoryResolver,
  Input,
  OnChanges,
  SimpleChanges,
  ApplicationRef,
  Injector,
  ComponentRef,
  Renderer2,
  AfterViewInit,
  TemplateRef,
  Optional,
} from '@angular/core';
import { Subject } from 'rxjs';
import { ContentObserver } from '@angular/cdk/observers';
import { takeUntil, filter, map } from 'rxjs/operators';
import { ComponentPortal, DomPortalOutlet } from '@angular/cdk/portal';
import { LvConfig } from '../core/config';
import { PositionType, ThemeType } from '../core/tooltipbase/api';
import { InputBoolean, InputNumber } from '../core/utils/convert';
import { ContextService } from '../core/services/context.service';
import { TooltipComponent } from '../tooltip/api';
import { ResizeObserverService } from '../core/services/resize-observer.service';
import { LvOverflowType, LvDirection, MeasureObj, LvDirectionEnum } from './interface';

@Directive({
  selector: '[lvOverflow], [lv-overflow]',
  exportAs: 'lvOverflow',
  host: {
    class: 'lv-overflow',
    '[class.lv-overflow-blank]': 'lvKeepWhiteSpace',
    '[class.lv-overflow-mid]': 'lvEllipsisDirection === lvDirectionEnum.MID',
    '(mouseenter)': 'toggleOpen(true)',
    '(mouseleave)': 'toggleOpen(false)',
    '(click)': 'toggleOpen(true)',
  },
})
export class OverflowDirective implements OnInit, OnChanges, OnDestroy, AfterViewInit {
  @Input() lvExecute = true; // NEED_DO:是否执行overflow指令，用于特殊场，后续废弃，使用lv-overflow

  @Input() lvSuffix: string; // 新增的后缀字符

  @Input() @InputNumber() lvRows = 1; // 省略的行数，默认值是1

  // 输入输出属性
  @Input() lvType: LvOverflowType = 'tooltip'; // 内容提示的类型默认title

  @Input() @InputNumber() lvSuffixCount: number; // 中间省略时的后缀字符数

  @Input('lvTooltipClassName') lvClassName: string; // overlay样式类

  @Input() lvContentTpl: TemplateRef<any> | string; // 自定义提示样式，主要使用自动tips

  @Input() lvEllipsisDirection: LvDirection = 'end'; // 省略号的方向

  @Input() @InputBoolean() lvKeepWhiteSpace: boolean; // 是否保留字符的所有空格

  @Input('lv-overflow') @InputBoolean() lvOverflow = true; // 是否执行overflow指令

  @Input('lvTooltipPosition') lvPosition: PositionType = 'top'; // 展示位置

  @Input('lvTooltipTheme') lvTheme: ThemeType =
    LvConfig.overflowOptions.lvTheme || LvConfig.tooltipOptions.lvTheme || 'light'; // 主题颜色

  // 外部可访问变量
  overlayRef;
  lvDirectionEnum = LvDirectionEnum;

  _componentRef: ComponentRef<TooltipComponent>;

  private _classMap = new Map();
  private _activeResizeObserver;
  private _isMidOverflow = false;
  private _defaultEllipsisRow = 1;
  private _midEllipsisFullText = '';
  private _portalHost: DomPortalOutlet;
  // 内部变量
  private _destroy$ = new Subject<void>();
  private _open$ = new Subject<boolean>();
  private _over$ = new Subject<boolean>(); // 标识是否溢出
  private _show$ = new Subject<boolean>(); // 标识是否显示
  private _content$ = new Subject<string>();
  private _overflowClass = 'lv-text-overflow';
  private _multipleRowsClass = 'lv-multiple-rows';
  private _activeDom = this._element.nativeElement;
  private _componentPortal: ComponentPortal<TooltipComponent>;
  constructor(
    private _element: ElementRef,
    private _renderer: Renderer2,
    private _observer: ContentObserver,
    private _viewContainer: ViewContainerRef,
    private _appRef: ApplicationRef,
    private _injector: Injector,
    private _componentFactoryResolver: ComponentFactoryResolver,
    private _resizeObserverService: ResizeObserverService,
    @Optional() public contextService: ContextService,
  ) {
    this._classMap.set(this._overflowClass, true);
    this.toggleOverflowClass(true);
  }

  get isTitle(): boolean {
    return this.lvType === 'title';
  }

  getTextContent(): any {
    return this._activeDom.textContent;
  }

  _setActiveContent(text: string): void {
    this._activeDom.innerText = text;
  }

  // 删除title
  removeTitle(): void {
    this._portalHost.detach();
    this._activeDom.title = '';
  }

  // 省略号出现在中间位置
  _isMidDirection(): boolean {
    return this.lvEllipsisDirection === this.lvDirectionEnum.MID;
  }

  // 省略号出现在开始或结束位置，多行
  _isStartAndEndRows(): boolean {
    return !this._isMidDirection() && this.lvRows > this._defaultEllipsisRow;
  }

  toggleOpen(open: boolean): void {
    if (!this.lvOverflow) {
      return;
    }
    this._open$.next(open);
  }

  updateTooltipContent(content: string): void {
    this._componentRef.instance.lvContent = this.lvContentTpl || content;
  }

  _inRange(referNode: HTMLElement, lineHeight: number, row: number): boolean {
    return referNode.offsetHeight > row * lineHeight;
  }

  getBox(dom: HTMLElement): { width: number; height: number } {
    const { width, height } = dom.getBoundingClientRect();
    return { width, height };
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
    this._activeResizeObserver?.unobserve();
  }

  _setText(text: string): HTMLElement {
    const textNode = this._renderer.createText(text);
    this._activeDom.appendChild(textNode);
    return textNode;
  }

  // 动态添加title
  appendTitle(value: string): void {
    // 仅当lvContentTpl是string时可用
    this._activeDom.title = typeof this.lvContentTpl === 'string' ? this.lvContentTpl : value;
  }

  updateContent(content: string): void {
    const { isTitle } = this;
    if (isTitle) {
      this.appendTitle(content);
    } else {
      this.updateTooltipContent(content);
    }
  }

  createPortalHost(): void {
    this._portalHost = new DomPortalOutlet(
      this._activeDom as HTMLElement,
      this._componentFactoryResolver,
      this._appRef,
      this._injector,
    );
  }

  _setElement(text: string): HTMLElement {
    const elemntNode = this._renderer.createElement('span');
    elemntNode.innerText = text;
    this._activeDom.appendChild(elemntNode);
    return elemntNode;
  }

  _subscribeOpen(): void {
    this._show$
      .pipe(
        filter((): boolean => !this.isTitle),
        takeUntil(this._destroy$),
      )
      .subscribe((res): void => {
        this._componentRef.instance.tooltipShow$.next(res);
      });
  }

  toggleOverflowClass(add: boolean): void {
    this._classMap.forEach((value, key): void => {
      if (!add) {
        this._renderer.removeClass(this._activeDom, key);
      } else {
        this._renderer.addClass(this._activeDom, key);
      }
    });
  }

  // 动态添加tooltip组件
  appendTooltip(): void {
    this._componentPortal = new ComponentPortal(TooltipComponent, this._viewContainer);
    this._componentRef = this._portalHost.attachComponentPortal(this._componentPortal);
    this._componentRef.changeDetectorRef.markForCheck();
  }

  createTooltipPortal(): void {
    this.createPortalHost();
    this.appendTooltip();
    this._componentRef.instance.lvOrigin = this._activeDom;
    this._componentRef.instance.lvTheme = this.lvTheme;
    this._componentRef.instance.lvPosition = this.lvPosition;
    this._componentRef.instance.lvClassName = this.lvClassName;
    this.removeTipElement();
  }

  _dealSuffixCount(lineHeight: number, initText: string): void {
    this._midEllipsisFullText = initText;
    const startText = this._midEllipsisFullText.slice(0, this._midEllipsisFullText.length - this.lvSuffixCount).trim();
    const suffixText = this._midEllipsisFullText.slice(-this.lvSuffixCount).trim();
    this._measureEllipsis(lineHeight, startText, suffixText);
  }

  _calcExcludeLastRow(lineHeight: number, fullText: string): string {
    const rowNode = this._setElement(fullText);
    const step = this._measureText({
      referNode: this._activeDom,
      lineHeight,
      measureNode: rowNode,
      fullText,
      measureRows: this.lvRows - 1,
      isFromEndToStart: false,
    } as MeasureObj);
    return fullText.slice(step - 1);
  }

  _dealMidOverflow(lineHeight: number, initText: string): void {
    if (this.lvSuffix !== undefined) {
      this._dealSuffix(lineHeight, initText);
      return;
    }

    this._isMidOverflow = this._inRange(this._activeDom, lineHeight, this.lvRows);
    if (!this._isMidOverflow) {
      return;
    }

    if (this.lvSuffixCount !== undefined) {
      this._dealSuffixCount(lineHeight, initText);
      return;
    }

    this._dealDefault(lineHeight, initText);
  }

  _dealSuffix(lineHeight: number, initText: string): void {
    this._midEllipsisFullText = `${initText}${this.lvSuffix}`;
    this._setActiveContent(this._midEllipsisFullText);
    this._isMidOverflow = this._inRange(this._activeDom, lineHeight, this.lvRows);
    if (!this._isMidOverflow) {
      return;
    }
    // 如果加上后缀字符不能显示完整，后缀字符不能省略，完整宽度为initText省略后的内容+lvSuffix
    this._setActiveContent(initText);
    this._measureEllipsis(lineHeight, initText, this.lvSuffix);
  }

  ngOnInit(): void {
    // overflow自身的配置 > (表格td局部配置 > 表格datatable局部配置 > 表格全局配置)(this.contextService?.keepWhiteSpace) > overflow全局的配置
    if (this.lvKeepWhiteSpace === undefined) {
      this.lvKeepWhiteSpace =
        this.contextService?.keepWhiteSpace === undefined
          ? LvConfig.overflowOptions.lvKeepWhiteSpace
          : this.contextService?.keepWhiteSpace;
    }
    if (!this.isTitle) {
      this.createTooltipPortal();
    }
    this.refreshContent();
    this._subscribeOpen();
  }

  _dealDefault(lineHeight: number, initText: string): void {
    this._setActiveContent('');
    if (this.lvRows > 1) {
      // 如果未配置后缀字符且多行的话需要计算前面有哪些字符放在非截断的行
      this._midEllipsisFullText = this._calcExcludeLastRow(lineHeight, initText);
    } else {
      this._midEllipsisFullText = initText;
    }
    const halfLength = this._midEllipsisFullText.length / 2;
    const startText = this._midEllipsisFullText.slice(0, halfLength).trim();
    const suffixText = this._midEllipsisFullText.slice(-halfLength).trim();
    const halfWidth = (this._activeDom.clientWidth - 10) / 2; // 10表示省略号的宽度
    this._measureEllipsis(lineHeight, startText, suffixText, halfWidth);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvExecute) {
      this.lvOverflow = changes.lvExecute.currentValue;
    }

    if (changes.lvRows) {
      if (this._isStartAndEndRows()) {
        this._classMap.set(this._multipleRowsClass, true);
        this._renderer.addClass(this._activeDom, this._multipleRowsClass);
        this._renderer.setStyle(this._activeDom, '-webkit-line-clamp', changes.lvRows.currentValue);
      } else {
        this._classMap.delete(this._multipleRowsClass);
        this._renderer.removeClass(this._activeDom, this._multipleRowsClass);
        this._renderer.removeStyle(this._activeDom, '-webkit-line-clamp');
      }
    }
  }

  ngAfterViewInit(): void {
    if (this.lvEllipsisDirection === this.lvDirectionEnum.START) {
      this._renderer.addClass(this._activeDom, 'lv-front-line');
    }

    if (this._isMidDirection()) {
      this.toggleOverflowClass(false);
      const lineHeight = parseInt(window.getComputedStyle(this._activeDom).getPropertyValue('line-height'));
      const initText = this._activeDom.textContent.trim();
      this._dealMidOverflow(lineHeight, initText);

      // 初始化之后自动响应宽度的变化
      let initWidth = this._activeDom.clientWidth;
      this._activeResizeObserver = this._resizeObserverService.observe(this._activeDom, (): void => {
        if (initWidth !== this._activeDom.clientWidth) {
          this._dealMidOverflow(lineHeight, initText);
          initWidth = this._activeDom.clientWidth;
        }
      });
      return;
    }
  }

  _measureText(measureObj: MeasureObj, startLoc: number = 0, endLoc: number = measureObj.fullText.length): any {
    const { referNode, lineHeight, measureNode, fullText, measureRows, isFromEndToStart } = measureObj;
    const midLoc = Math.floor((startLoc + endLoc) / 2);
    let content = '';
    if (isFromEndToStart) {
      content = fullText.slice(startLoc === midLoc ? endLoc : midLoc);
    } else {
      content = fullText.slice(0, midLoc);
    }
    measureNode.innerText = content;
    if (startLoc >= endLoc - 1) {
      if (!this._inRange(referNode, lineHeight, measureRows)) {
        return endLoc;
      }
    }
    if (this._inRange(referNode, lineHeight, measureRows)) {
      return this._measureText(measureObj, isFromEndToStart ? midLoc : startLoc, isFromEndToStart ? endLoc : midLoc);
    } else {
      return this._measureText(measureObj, isFromEndToStart ? startLoc : midLoc, isFromEndToStart ? midLoc : endLoc);
    }
  }

  refreshContent(): void {
    const dom = this._activeDom;
    this._open$
      .pipe(
        filter((): boolean => !!this._componentRef),
        filter((res): any => {
          if (!res) {
            this._show$.next(false);
          }
          return res;
        }),
        map((): any => this.getTextContent()),
        takeUntil(this._destroy$),
      )
      .subscribe((res: string): void => {
        if (this._isMidDirection()) {
          this.updateContent(this._isMidOverflow ? this._midEllipsisFullText : '');
          this._show$.next(this._isMidOverflow);
          return;
        }
        this.toggleOverflowClass(false);
        const preBox = this.getBox(dom);
        this.toggleOverflowClass(true);
        const currentBox = this.getBox(dom);

        if (preBox.height > currentBox.height || preBox.width > currentBox.width) {
          this.updateContent(res);
          this._show$.next(true);
        } else {
          this.updateContent('');
          this._show$.next(false);
        }
      });
  }

  _measureEllipsis(lineHeight: number, startText: string, suffixText: string, halfWidth?: number): void {
    const startNode = this._renderer.createElement('span');
    startNode.innerText = startText;
    if (halfWidth) {
      startNode.style.width = `${halfWidth}px`;
      startNode.style.display = 'inline-block';
    } else {
      this._setActiveContent('');
    }
    this._activeDom.appendChild(startNode);
    if (halfWidth) {
      // 未配置后缀字符需自动计算时，只最后一行出省略号
      this._measureText({
        referNode: startNode,
        lineHeight,
        measureNode: startNode,
        fullText: startText,
        measureRows: 1,
        isFromEndToStart: false,
      } as MeasureObj);
      this._setText('...');
      const suffixNode = this._setElement(suffixText);
      suffixNode.style.width = `${halfWidth}px`;
      suffixNode.style.display = 'inline-block';
      this._measureText({
        referNode: suffixNode,
        lineHeight,
        measureNode: suffixNode,
        fullText: suffixText,
        measureRows: 1,
        isFromEndToStart: true,
      } as MeasureObj);
    } else {
      this._setText(`...${suffixText}`);
      this._measureText({
        referNode: this._activeDom,
        lineHeight,
        measureNode: startNode,
        fullText: startText,
        measureRows: this.lvRows,
        isFromEndToStart: false,
      } as MeasureObj);
    }
  }

  private removeTipElement(): void {
    if (this._componentRef?.location?.nativeElement) {
      // 删除组件的DOM，因为它应该在overlay容器中。
      this._renderer.setStyle(this._componentRef.location.nativeElement, 'display', 'none');
    }
  }
}
